//
//  RuntimeTest.m
//  JustForTest
//
//  Created by YaoZhaofeng on 16/1/20.
//  Copyright © 2016年 YaoZhaofeng. All rights reserved.
//

#import "XYMessageTest.h"
#import <objc/runtime.h>
#import "CategoryObject.h"


void myDynamicMethodIMP(id self, SEL _cmd){
    NSLog(@"name::%@",NSStringFromSelector(_cmd));
}

@implementation XYMessageTest
/**
 *  动态方法解析，当Runtime系统在类（包含父类）的cache和方法分发列表中都找不到要执行的方法时候，Runtime回调该方法，给我们一次动态添加方法实现的机会，我们可以用class_addMethod函数完成向特定类添加特定方法实现的操作
 *
 *  @param sel 方法选择器
 *
 *  @return NO:该方法选择器将会被传送到转发机制
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(myDynamicMethod)) {
        class_addMethod([self class], sel, (IMP)myDynamicMethodIMP,"v@:");
        return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}

/**
 *  方法重定向，当某个类不响应某个消息的时候，可以在这个方法中改变消息的接受者（该方法会在消息转发机制执行前执行）
 *
 *  @param aSelector 方法选择器
 *
 *  @return 重定向的消息接受者
 */
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(doSomething)){
        NSLog(@"我不能响应doSomething，将由CategoryObject来响应");
        return [[CategoryObject alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

/**
 *  获取方法签名，用于生成NSInvocation对象并做消息转发
 *
 *  @param aSelector 方法选择器
 *
 *  @return 方法签名
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"获取方法%@的方法签名，用于生成NSInvocation对象并做消息转发", NSStringFromSelector(aSelector));
    
    NSMethodSignature *sig = [CategoryObject instanceMethodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
    }
    
    return sig;
}

/**
 *  消息转发（当动态方法解析不作处理返回NO时，消息转发会被触发）
 *
 *  我们可以实现该方法对不能处理的消息做一些默认的处理，也可以将消息转发给其他对象，而不抛出错误。
 *
 *  @param anInvocation 该对象封装了原始的消息和消息的参数
 *         该参数的来历：在forwardInvocation:消息发送前，Runtime系统会向对象发送methodSignatureForSelector:消息，并取到返回的方法签名用于生成NSInvocation对象。所以我们在重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法，否则会抛异常。
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([[[CategoryObject alloc] init] respondsToSelector:[anInvocation selector]]) {
        NSLog(@"将消息转发给CategoryObject的对象来执行");
        [anInvocation invokeWithTarget:[[CategoryObject alloc] init]];
    }
    else{
//        [super forwardInvocation:anInvocation];
        NSLog(@"找不到该方法，此处给忽略，不让崩溃");
    }
}

@end
